﻿using Medallion.Threading;
using Microsoft.EntityFrameworkCore;
using Quartz;

namespace Application.Job
{
    public class AssignJob : IJob
    {
        private readonly ISchedulerFactory _schedulerFactory;
        private readonly JobManagerDbContext _dbContext;
        private readonly IDistributedLockProvider _synchronizationProvider;
        public AssignJob(JobManagerDbContext dbContext, ISchedulerFactory schedulerFactory, IDistributedLockProvider synchronizationProvider)
        {
            _dbContext = dbContext;
            _schedulerFactory = schedulerFactory;
            _synchronizationProvider = synchronizationProvider;
        }

        public async Task Execute(IJobExecutionContext context)
        {
            var scheduler = await _schedulerFactory.GetScheduler();
            await scheduler.Start();
            var servers = _dbContext.KeyValue.Where(s => s.Type == KeyValueType.Server).ToList();

            await AssignJobTask(servers, scheduler);
        }
        //启用、新增时
        private async Task AssignJobTask(List<KeyValue> servers, IScheduler scheduler)
        {
            await Task.Delay(TimeSpan.FromSeconds(new Random().Next(0, 3)));//随机暂停n秒，比秒多实例时，某一实例总能抢占锁
            using (this._synchronizationProvider.AcquireLock($"UserAccount{Resource.AssignJob}"))
            {
                var thisServer = servers.FirstOrDefault(s => s.Key == GlobalData.ServerKey);
                if (thisServer == null) { return; }
                var jobServerGroup = await _dbContext.JobTask.Where(s => s.ServerId != 0).GroupBy(s => s.ServerId).Select(s => new { s.Key, Count = s.Count() }).ToListAsync();

                foreach (var item in servers.Where(s => !jobServerGroup.Select(s => s.Key).Contains(s.Id)))
                {
                    jobServerGroup.Add(new { Key = item.Id, Count = 0 });
                }

                var unAssignjobs = _dbContext.JobTask.Where(s => s.ServerId == 0 && s.IsEnable).ToList();
                //1.待分配任务，重新分配
                var assignedjobServerIds = jobServerGroup.Select(s => s.Key).Distinct().ToList();
                var sum = jobServerGroup.Select(s => s.Count).Sum();
                var unAssignServerCount = servers.Count(s => !assignedjobServerIds.Contains(s.Id));
                var total = sum + unAssignjobs.Count();
                var thisServerJobCount = jobServerGroup.FirstOrDefault(s => s.Key == thisServer.Id)?.Count ?? 0;
                var avgCount = Convert.ToInt32(Math.Ceiling(total / (decimal)servers.Count()));
                var needAssignCount = avgCount - thisServerJobCount;
                var needAssignJobs = unAssignjobs.Take(needAssignCount).ToList();

                foreach (var item in needAssignJobs)
                {
                    var jobId = item.Id.ToString();
                    await scheduler.RebuildJob(new JobKey(jobId, JobGroupName.Task), item.Cron);

                    item.ServerId = thisServer.Id;
                    item.NextExecutionTime = SchedulerHelper.NextExecutionTime(item.Cron);
                }
                //2. 释放本实例多余的任务：调度任务数量最大的服务，下次执行的时间大于5分钟的差数达serverCount*5个，则重新分配
                var assignedjobServerGroupDesByCount = jobServerGroup.OrderBy(s => s.Count);
                var range = assignedjobServerGroupDesByCount.LastOrDefault()?.Count - assignedjobServerGroupDesByCount.FirstOrDefault()?.Count;//最大值-最小值
                if (thisServerJobCount == assignedjobServerGroupDesByCount.LastOrDefault()?.Count)//当本实例job数量最多时
                {
                    var needDisposeCount = (int)(range - avgCount);
                    //综合计算需要分配: 差值大于servers.Count * 5
                    if (needDisposeCount > 0 && range > servers.Count * 5)
                    {
                        var thisServerJobs = _dbContext.JobTask.Where(s => s.ServerId == GlobalData.ServerId && s.NextExecutionTime > DateTime.Now.AddMinutes(5)).ToList();
                        var needDisposeJobs = thisServerJobs.Take(needDisposeCount).ToList();
                        needDisposeJobs.ForEach(s => s.ServerId = 0);
                        await Console.Out.WriteLineAsync($"释放本实例多余的任务：{needDisposeJobs.Select(s => s.Id).JoinString()}");
                    }
                }

                _dbContext.SaveChanges();
            }
        }
    }


    public class JobGroupName
    {
        public const string System = "system";
        public const string Task = "task";
        public const string Temp = "temp";
    }
}
